#include <iostream>
#include <string>
#include <stack>
#include <vector>
#include <string.h>
#include "AATBuildTree.h"
#include "hash.h"
#include "../StaticAnalysis/static.hpp"

char* CurrType;
stack<int> MemSize;
vector<char*> MemBlocks;
vector<H_hashTable> VarTable;
vector<H_hashTable> ConstTable;
vector<H_hashTable> VarTypeTable;
H_hashTable TypeSizeTable;
H_hashTable RecordOffsetTable;
H_hashTable ArrayTable;
H_hashTable MemBlockOrder;


extern static map<string, string> record_list;
extern static map<string, string> array_list;


typedef struct
{
    int LIndex, RIndex, ESize;
} Array_info;

typedef struct
{
    H_hashTable Consts, Vars, VarTypes;
} MemBlockArrangement;

typedef struct
{
    bool found;
    int Offset;
    char* blockName;
} MemLoc;

MemLoc GetVarOffset(char* VarName)
{
    MemLoc returnVal;
    int count = VarTable.size() - 1;
    vector<H_hashTable>::reverse_iterator it;
    for (it = VarTable.rbegin();it<VarTable.rend();it++)
    {
        void* res = H_find(*it, VarName);
        if (res != NULL)
        {
            returnVal.found = true;
            returnVal.Offset = (int) *res;
            returnVal.blockName = *(MemBlocks.begin() + count);
            return returnValue;
        }
        count--;
    }
    returnVal.found = false;
    returnVal.Offset = 0;
    returnVal.blockName = (char*) " ";
    return returnVal;
}

MemLoc GetConstOffset(char* ConstName)
{
    MemLoc returnVal;
    int count = ConstTable.size() - 1;
    vector<H_hashTable>::reverse_iterator it;
    for (it = ConstTable.rbegin();it<ConstTable.rend();it++)
    {
        void* res = H_find(*it, ConstName);
        if (res != NULL)
        {
            returnVal.found = true;
            returnVal.Offset = (int) *res;
            returnVal.blockName = *(MemBlocks.begin() + count);
            return returnVal;
        }
        count --;
    }
    returnVal.found = false;
    returnVal.Offset = 0;
    returnVal.blockName = (char*) " ";
    return returnVal;
}

void InitTypeTable()
{
    int boolSize = 4;
    int intSize = 4;

    H_insert(TypeSizeTable, (char*) "boolean", (void*) *boolSize);
    H_insert(TypeSizeTable, (char*) "integer", (void*) *intSize);
}

void GetOffset(tree_typed* StatTree, H_hashTable* tab, bool isVar)
{
    void* TypeSize = H_find(TypeSizeTable, StatTree->an_operateur.first->a_variable);
    tree_typed *temp = StatTree->an_operateur.second;
    int* Offset;
    if (temp->nodetype != variable_node)
    {
        while (temp->an_operateur.second->nodetype != variable_node)
        {
            *Offset = MemSize.top();
            H_insert(*tab, temp->an_operateur.first->a_variable, (void*) Offset);
            if (isVar)
            {
                H_insert(VarTypeTable.back(), StatTree->an_operateur.first->a_variable, (void*));
            }
            MemSize.top() += (int) *TypeSize;
            temp = temp->an_operateur.second;
        }
        *Offset = MemSize.top();
        H_insert(*tab, temp->an_operateur.first->a_variable, (void*) Offset);
        if (isVar)
        {
            H_insert(VarTypeTable.back(), StatTree->an_operateur.first->a_variable, (void*));
        }
        MemSize.top() += (int) *TypeSize;
    }
    else
    {
        *Offset = MemSize.top();
        H_insert(*tab, temp->a_variable, (void*) Offset);
        if (isVar)
        {
            H_insert(VarTypeTable.back(), StatTree->an_operateur.first->a_variable, (void*));
        }
        MemSize.top() += (int) *TypeSize;
    }
}

void GetField(tree_typed* StatTree, H_hashTable* tab)
{
    tree_typed *temp = *StatTree;
    if (temp->an_operateur.operateur == RECORDLIST_OP)
    {
        while (temp->an_operateur.second->an_operateur.operateur != RECORDIDLIST_OP)
        {
            GetOffset(StatTree->an_operateur.first, tab, false);
            temp = temp->an_operateur.second;
        }
        GetOffset(StatTree->an_operateur.first, tab, false);
        GetOffset(StatTree->an_operateur.second, tab, false);
    }
    else
    {
        GetOffset(StatTree, tab, false);
    }
}

int GetTypeSize(tree_typed* StatTree, char* TypeName)
{
    switch (StatTree->an_operateur.operateur)
    {
        case ARRAYDEF_OP:
        {
            void* TypeSize = H_find(TypeSizeTable, StatTree->an_operateur.second->a_variable);
            Array_info* nfo = new Array_info;
            nfo->ESize = (int) *TypeSize;
            if (StatTree->an_operateur.first->an_operateur.first->nodetype == number_node)
            {
                nfo->LIndex = StatTree->an_operateur.first->an_operateur.first->a_number;
            }
            else if (StatTree->an_operateur.first->an_operateur.first->nodetype == boolean_node)
            {
                nfo->LIndex = StatTree->an_operateur.first->an_operateur.first->a_boolean;
            }
            else if (StatTree->an_operateur.first->an_operateur.first->nodetype == variable_node)
            {

                void* Const = H_find(ConstTable.back(), StatTree->an_operateur.first->an_operateur.first->a_variable);
                nfo->LIndex = (int) *Const;
            }

            if (StatTree->an_operateur.first->an_operateur.second->nodetype == number_node)
            {
                nfo->RIndex = StatTree->an_operateur.first->an_operateur.second->a_number;
            }
            else if (StatTree->an_operateur.first->an_operateur.second->nodetype == boolean_node)
            {
                nfo->RIndex = StatTree->an_operateur.first->an_operateur.second->a_boolean;
            }
            else if  (StatTree->an_operateur.first->an_operateur.second->nodetype == variable_node)
            {
                int StatTree->an_operateur.first->an_operateur.second->a_variable);
                nfo->RIndex = (int) *Const;
            }
            H_insert(ArrayTable, TypeName, (void*) nfo);
            return ((nfo->RIndex - nfo->LIndex + 1) * nfo->ESize);
        }
        break;
        case RECORDDEF_OP:
        {
            H_hashTable* FieldTable;
            *FieldTable = H_HashTable(256);
            MemSize.push(0);

            GetField(StatTree->an_operateur.first, FieldTable);
            H_insert(RecordOffsetTable, TypeName, (void*) FieldTable);

            int res = MemSize.top();
            MemSize.pop();
            return res;
        }
        break;
    }
}

void ConstParser(tree_typed* StatTree)
{
    switch (StatTree->an_operateur.operateur)
    {
        case CONSTLIST_OP:
        {
            if (StatTree->an_operateur.first != NULL)
            {
                ConstParser(StatTree->an_operateur.first);
            }
            if (StatTree->an_operateur.second != NULL)
            {
                ConstParser(StatTree->an_operateur.second);
            }
        }
        break;
        case CONSTDEF_OP:
        {
            void* data = NULL;
            switch (StatTree->an_operateur.second->nodetype)
            {
                case number_node: data = ConstantIntExpression(StatTree->an_operateur.second->a_number); break;
                case boolean_node: data = ConstantBoolExpression(StatTree->an_operateur.second->a_boolean); break;
                case variable_node:
                {
                    data = H_find(ConstTable, StatTree->an_operateur.second->a_variable); break;
                }
                break;
            }
            H_insert(ConstTable, StatTree->an_operateur.first->a_variable, data);
        }
        break;
    }
}

void TypeDefParser(tree_typed* StatTree)
{
    switch (StatTree->an_operateur.operateur)
    {
        case TYPEDEFLIST_OP:
        {
            if (StatTree->an_operateur.first != NULL)
            {
                TypeDefParser(StatTree->an_operateur.first);
            }
            if (StatTree->an_operateur.second != NULL)
            {
                TypeDefParser(StatTree->an_operateur.second);
            }
        }
        break;
        case TYPEDEF_OP:
        {
            int *TypeSize = GetTypeSize(StatTree->an_operateur.second, StatTree->an_operateur.first->a_variable);
            H_insert(TypeSizeTable, StatTree->an_operateur.first->a_variable, (void*) TypeSize);
        }
    }
}

void VarParser(tree_typed* StatTree)
{
    tree_typed *temp = *StatTree;
    if (temp->an_operateur.operateur == VARLIST_OP)
    {
        while (temp->an_operateur.second->an_operateur.operateur != VARTYPE_OP)
        {
            GetOffset(StatTree->an_operateur.first, VarTable.back(), true);
            temp = temp->an_operateur.second;
        }
        GetOffset(StatTree->an_operateur.first, VarTable.back(), true);
        GetOffset(StatTree->an_operateur.second, VarTable.back(), true);
    }
    else
    {
        GetOffset(StatTree, VarTable.back(), true);
    }
}

void ParamParser(tree_typed* StatTree*)

AATstatement InitNodeConversion(tree_typed* StatTree)
{
    MemSize.push(0);
    H_hashTable NewVarTable = H_HashTable(4096);
    VarTable.push_back(NewVarTable);
    TypeSizeTable = H_HashTable(2048);
    InitTypeTable();
    RecordOffsetTable = H_HashTable(2048);
    ArrayLIndexTable = H_HashTable(2048);
    ConstTable = H_HashTable(2048);

    ConstParser(StatTree->an_operateur.first->an_operateur.first);
    TypeDefParser(StatTree->an_operateur.first->an_operateur.second);
    VarParser(StatTree->an_operateur.first->an_operateur.third);

    ATTstatement res = SequentialStatement(TreeConversionOPstat(StatTree->an_operateur.second), TreeConversionOPstat(StatTree->an_operateur.third));

    MemoryAllocation((Label) "main", MemSize.top());
    MemSize.pop();
    return res;
}

int VarAccessOffset(tree_typed* StatTree)
{
    int Offset = 0;
    if (StatTree->nodetype == variable_node)
    {
        Offset = GetVarOffset(StatTree->a_variable);
        return Offset;
    }
    else if (StatTree->an_operateur.operateur == ARRAYACCESS_TYPE)
    {
        Offset = VarAccessOffset(StatTree->first);
    }
}

AATexpression TreeConversionOPexpr(tree_typed* StatTree)
{
    if (StatTree->nodetype == operateur_node)
    {
        switch (StatTree->an_operateur.operateur)
        {
            case LT_OP: return OperatorExpression(StatTree->an_operateur.first, StatTree->an_operateur.second, AAT_LT); break;
            case GT_OP: return OperatorExpression(StatTree->an_operateur.first, StatTree->an_operateur.second, AAT_GT); break;
            case EQUAL_OP: return OperatorExpression(StatTree->an_operateur.first, StatTree->an_operateur.second, AAT_EQ); break;
            case LE_OP: return OperatorExpression(StatTree->an_operateur.first, StatTree->an_operateur.second, AAT_LEQ); break;
            case GE_OP: return OperatorExpression(StatTree->an_operateur.first, StatTree->an_operateur.second, AAT_GEQ); break;
            case NOTEQUAL_OP: return OperatorExpression(StatTree->an_operateur.first, StatTree->an_operateur.second, AAT_NEQ); break;
            case PLUS_OP: return OperatorExpression(StatTree->an_operateur.first, StatTree->an_operateur.second, AAT_PLUS); break;
            case MINUS_OP: return OperatorExpression(StatTree->an_operateur.first, StatTree->an_operateur.second, AAT_MIN); break;
            case OR_OP: return OperatorExpression(StatTree->an_operateur.first, StatTree->an_operateur.second, AAT_PLUS); break;
            case STAR_OP: return OperatorExpression(StatTree->an_operateur.first, StatTree->an_operateur.second, AAT_MULTIPLY); break;
            case DIV_OP: return OperatorExpression(StatTree->an_operateur.first, StatTree->an_operateur.second, AAT_DIVIDE; break;
            case MOD_OP: return OperatorExpression(StatTree->an_operateur.first, StatTree->an_operateur.second, AAT_MODULO); break;
            case AND_OP: return OperatorExpression(StatTree->an_operateur.first, StatTree->an_operateur.second, AAT_MULTIPLY); break;
            case NOT_OP: return OperatorExpression(StatTree->an_operateur.first, StatTree->an_operateur.second, AAT_NOT); break;
            case ARRAYACCES_OP:
            {
                MemLoc Loc = GetVarOffset(StatTree->an_operateur.first->a_variable);
                char* TypeName = (char*) GetVarType(StatTree->an_operateur.first->a_variable);
                struct Array_info* info = (struct Array_info*) H_find(ArrayTable, (char*) TypeName);
                return ArrayVariable((Label) Loc.blockName, Loc.Offset, ConstantIntExpression(info->LIndex), TreeConversionOPexpr(StatTree->an_operateur.second), info->ESize);
            }
            break;
            case RECORDACCES_OP
            {
                MemLoc Loc = GetVarOffset(StatTree->an_operateur.first->a_variable);
                char* TypeName = (char*) GetVarType(StatTree->an_operateur.first->a_variable);
                H_hashTable* model = (H_hashTable*) H_find(RecordTable, TypeName);
                int* Offset = (int*) H_find(*model, StatTree->an_operateur.second->a_variable);
                return RecordVariable((Label) Loc.blockName, Loc.Offset, *Offset);
            }
            break;
        }
    }
    else if (StatTree->nodetype == variable_node)
    {
        MemLoc Loc = GetConstOffset(StatTree->a_variable);
        if (Loc.found)
        {
            return H_find(ConstTable, StatTree->a_variable);
        }
        else
        {
            char* TypeName = (char*) GetVarType(StatTree->a_variable);
            Loc = GetVarOffset(StatTree->a_variable);
            if (strncmp(TypeName, "boolean") == 0)
            {
                return BooleanVariable((Label) Loc.blockName, Loc.Offset);
            }
            else
            {
                return IntegerVariable((Label) Loc.blockName, Loc.Offset);
            }
        }
    }
    else if (StatTree->nodetype == boolean_node)
    {
        return ConstantBoolExpression(StatTree->a_boolean);
    }
    else
    {
        return ConstantIntExpression(StatTree->an_integer);
    }
}

AATexpressionList ArgListConversion(tree_typed* StatTree)
{
    if (StatTree->an_operateur.operateur == ARG_OP)
    {
        return AATExpressionList(TreeConversionOPexpr(StatTree->an_operateur.first), NULL);
    }
    else
    {
        return AATExpressionList(TreeConversionOPexpr(StatTree->an_operateur.first), ArgListConversion(StatTree->an_operateur.second));
    }
}

AATstatement TreeConversionOPstat(tree_typed* StatTree)
{
    switch(StatTree->an_operateur.operateur)
    {
        case Empty_OP: return EmptyStatement(); break;
        case BLOCKBODY_OP:
        {
            ConstParser(StatTree->an_operateur.first);
            TypeDefParser(StatTree->an_operateur.second);
            VarParser(StatTree->an_operateur.third);

            return SequentialStatement(TreeConversionOPstat(StatTree->an_operateur.second), TreeConversionOPstat(StatTree->an_operateur.third));
        }
        break;
        case ASSIGNMENT_OP: return AssignmentStatement(TreeConversionOPexpr(StatTree->an_operateur.first), TreeConversionOPexpr(StatTree->an_operateur.second)); break;
        case FUNCCALL_OP: return CallStatement((Label) StatTree->an_operateur.first->a_variable, ArgListConversion(StatTree->an_operateur.second)); break;
        case PROCCALL_OP: return CallStatement((Label) StatTree->an_operateur.first->a_variable, ArgListConversion(StatTree->an_operateur.second)); break;
        case IFTHEN_OP: return IfStatement(TreeConversionOPexpr(StatTree->an_operateur.first), TreeConversionOPexpr(StatTree->an_operateur.second), EmptyStatement()); break;
        case IFTHENELSE_OP:
        return IfStatement(TreeConversionOPexpr(StatTree->an_operateur.first), TreeConversionOPexpr(StatTree->an_operateur.second), TreeConversionOPexpr(StatTree->an_operateur.third)); break;
        case WHILE_OP: return WhileDoStatement(TreeConversionOPexpr(StatTree->an_operateur.first), TreeConversionOPstat(StatTree->an_operateur.second)); break;
        case COMPOUND_OP: return TreeConversionOPstat(StatTree->an_operateur.first); break;
        case STATEMENTLIST_OP: return SequentialStatement(TreeConversionOPstat(StatTree->an_operateur.second), TreeConversionOPstat(StatTree->an_operateur.first)); break;
        case STATEMENT_OP: return TreeConversionOPstat(StatTree->an_operateur.first); break;
        case PROCFUNCLIST_OP: return SequentialStatement(TreeConversionOPstat(StatTree->an_operateur.first, TreeConversionOPstat(StatTree->an_operateur.second)); break;
        case FUNCDEF_OP:
        {
            H_hashTable NewHash = H_HashTable(2048);
            VarTable.push_back(NewHash);
            ConstTable.push_back(NewHash);
            VarTypeTable.push_back(NewHash);
            MemSize.push(0);

            int PSize = ParamParser(StatTree->an_operateur.second->an_operateur.second);
            ATTstatement body = TreeConversionOPstat(StatTree->an_operateur.second->an_operateur.third):

            int MSize = MemSize.top();
            MemSize.pop();
            MemoryAllocation((Label) StatTree->an_operateur.first->a_variable, MSize);

            return ProcedureDefinition(body, MSize, (Label) StatTree->an_operateur.first->a_variable, (Label) strcat((char*) "end", (Label) StatTree->an_operateur.first->a_variable, 128));
        }

        // TODO
    }
}

